package com.kapcb.secure.configuration.filter;

import com.kapcb.common.exception.FrameworkException;
import com.kapcb.secure.configuration.annotation.ApiEncrypt;
import com.kapcb.secure.configuration.properties.ApiDecryptProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * <a>Title: CryptoFilter </a>
 * <a>Author: kapcb <a>
 * <a>Description: CryptoFilter <a>
 *
 * @author Kpacb
 * @version 1.0
 * @date 2024/5/4 10:06
 * @since 1.0
 */
@Slf4j
public class CryptoFilter implements ApplicationContextAware, Filter {

    private final ApiDecryptProperties apiDecryptProperties;

    private ApplicationContext applicationContext;

    public CryptoFilter(ApiDecryptProperties apiDecryptProperties) {
        this.apiDecryptProperties = apiDecryptProperties;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        boolean responseFlag = false;
        ServletRequest requestWrapper = null;
        ServletResponse responseWrapper = null;
        EncryptResponseBodyWrapper responseBodyWrapper = null;

        if (StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
            if (HttpMethod.PUT.matches(request.getMethod()) || HttpMethod.POST.matches(request.getMethod())) {
                String headerValue = request.getHeader(apiDecryptProperties.getHeaderFlag());
                ApiEncrypt apiEncrypt = getApiEncryptAnnotation(request);

                responseFlag = apiEncrypt != null && apiEncrypt.response();

                if (StringUtils.isNotBlank(headerValue)) {
                    // 请求解密
                    requestWrapper = new DecryptRequestBodyWrapper(request, apiDecryptProperties.getPrivateKey(), apiDecryptProperties.getHeaderFlag());
                } else {
                    // 是否有注解，有就报错，没有放行
                    if (apiEncrypt != null) {
                        HandlerExceptionResolver exceptionResolver = applicationContext.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
                        exceptionResolver.resolveException(
                                request, response, null, new FrameworkException(HttpStatus.FORBIDDEN.value(), "没有访问权限，请联系管理员授权"));
                        return;
                    }
                }
                // 判断是否响应加密
                if (responseFlag) {
                    responseBodyWrapper = new EncryptResponseBodyWrapper(response);
                    responseWrapper = responseBodyWrapper;
                }
            }
        }

        filterChain.doFilter(requestWrapper == null ? request : requestWrapper, responseWrapper == null ? response : responseWrapper);

        if (responseFlag) {
            servletResponse.reset();
            // 对原始内容加密
            String encryptContent = responseBodyWrapper.getEncryptContent(response, apiDecryptProperties.getPublicKey(), apiDecryptProperties.getHeaderFlag());
            // 对加密后的内容写出
            servletResponse.getWriter().write(encryptContent);
        }
    }

    /**
     * 获取 ApiEncrypt 注解
     */
    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
        RequestMappingHandlerMapping handlerMapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        // 获取注解
        try {
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
            if (mappingHandler != null) {
                Object handler = mappingHandler.getHandler();
                if (handler != null) {
                    // 从handler获取注解
                    if (handler instanceof HandlerMethod) {
                        HandlerMethod handlerMethod = (HandlerMethod) handler;
                        return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    @Override
    public void destroy() {
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
